/**
 * @author nttdocomo
 */
define(function(require) {
	aries.klass("aries.dom.AbstractHelper", aries.Class.extend({
		emptyTags : /^(?:br|frame|hr|img|input|link|meta|range|spacer|wbr|area|param|col)$/i,
		confRe : /tag|children|cn|html|tpl|tplData$/i,
		endRe : /end/i,

		attribXlat : {
			cls : 'class',
			htmlFor : 'for'
		},

		closeTags : {},

		/**
		 * Creates new DOM element(s) and appends them to el.
		 * @param {String/HTMLElement/Ext.Element} el The context element
		 * @param {Object/String} o The DOM object spec (and children) or raw HTML blob
		 * @param {Boolean} [returnElement] true to return a Ext.Element
		 * @return {HTMLElement/Ext.Element} The new node
		 */
		append : function(el, o, returnElement) {
			return this.doInsert(el, o, returnElement, 'beforeend', '', true);
		},

		doInsert : function(el, o, returnElement, pos, sibling, append) {
			var newNode = this.insertHtml(pos, aries.getDom(el), this.markup(o));
			return returnElement ? Ext.get(newNode, true) : newNode;
		},

		generateMarkup : function(spec, buffer) {
			var me = this, attr, val, tag, i, closeTags;

			if ( typeof spec == "string") {
				buffer.push(spec);
			} else if (_.isArray(spec)) {
				for ( i = 0; i < spec.length; i++) {
					if (spec[i]) {
						me.generateMarkup(spec[i], buffer);
					}
				}
			} else {
				tag = spec.tag || 'div';
				buffer.push('<', tag);

				for (attr in spec) {
					if (spec.hasOwnProperty(attr)) {
						val = spec[attr];
						if (!me.confRe.test(attr)) {
							if ( typeof val == "object") {
								buffer.push(' ', attr, '="');
								me.generateStyles(val, buffer).push('"');
							} else {
								buffer.push(' ', me.attribXlat[attr] || attr, '="', val, '"');
							}
						}
					}
				}

				// Now either just close the tag or try to add children and close the tag.
				if (me.emptyTags.test(tag)) {
					buffer.push('/>');
				} else {
					buffer.push('>');

					// Apply the tpl html, and cn specifications
					if (( val = spec.tpl)) {
						val.applyOut(spec.tplData, buffer);
					}
					if (( val = spec.html)) {
						buffer.push(val);
					}
					if (( val = spec.cn || spec.children)) {
						me.generateMarkup(val, buffer);
					}

					// we generate a lot of close tags, so cache them rather than push 3 parts
					closeTags = me.closeTags;
					buffer.push(closeTags[tag] || (closeTags[tag] = '</' + tag + '>'));
				}
			}

			return buffer;
		},

		/**
		 * Inserts an HTML fragment into the DOM.
		 * @param {String} where Where to insert the html in relation to el - beforeBegin, afterBegin, beforeEnd, afterEnd.
		 *
		 * For example take the following HTML: `<div>Contents</div>`
		 *
		 * Using different `where` values inserts element to the following places:
		 *
		 * - beforeBegin: `<HERE><div>Contents</div>`
		 * - afterBegin: `<div><HERE>Contents</div>`
		 * - beforeEnd: `<div>Contents<HERE></div>`
		 * - afterEnd: `<div>Contents</div><HERE>`
		 *
		 * @param {HTMLElement/TextNode} el The context element
		 * @param {String} html The HTML fragment
		 * @return {HTMLElement} The new node
		 */
		insertHtml : function(where, el, html) {
			var hash = {}, hashVal, setStart, range, frag, rangeEl, rs;

			where = where.toLowerCase();

			// add these here because they are used in both branches of the condition.
			hash['beforebegin'] = ['BeforeBegin', 'previousSibling'];
			hash['afterend'] = ['AfterEnd', 'nextSibling'];

			range = el.ownerDocument.createRange();
			setStart = 'setStart' + (this.endRe.test(where) ? 'After' : 'Before');
			if (hash[where]) {
				range[setStart](el);
				frag = range.createContextualFragment(html);
				el.parentNode.insertBefore(frag, where == 'beforebegin' ? el : el.nextSibling);
				return el[(where == 'beforebegin' ? 'previous' : 'next') + 'Sibling'];
			} else {
				rangeEl = (where == 'afterbegin' ? 'first' : 'last') + 'Child';
				if (el.firstChild) {
					range[setStart](el[rangeEl]);
					frag = range.createContextualFragment(html);
					if (where == 'afterbegin') {
						el.insertBefore(frag, el.firstChild);
					} else {
						el.appendChild(frag);
					}
				} else {
					el.innerHTML = html;
				}
				return el[rangeEl];
			}

			throw 'Illegal insertion point -> "' + where + '"';
		},

		/**
		 * Returns the markup for the passed Element(s) config.
		 * @param {Object} spec The DOM object spec (and children)
		 * @return {String}
		 */
		markup : function(spec) {
			if ( typeof spec == "string") {
				return spec;
			}

			var buf = this.generateMarkup(spec, []);
			return buf.join('');
		}
	}))
})
